/**********************************************************************

  Audacity: A Digital Audio Editor

  VSTWrapper.h

  Dominic Mazzoni

  Paul Licameli split from VSTEffect.h

**********************************************************************/
#ifndef __AUDACITY_VST_WRAPPER__
#define __AUDACITY_VST_WRAPPER__

#include "EffectInterface.h"
#include "CFResources.h"
#include "XMLTagHandler.h"

#include <wx/dynlib.h>
#include <wx/string.h>

#include <unordered_map>
#include <vector>
#include <mutex>
#include <thread>

#include "aeffectx.h"

typedef intptr_t (* dispatcherFn)(AEffect* effect, int opCode, int index, intptr_t value, void* ptr, float opt);

typedef void (* processFn)(AEffect* effect, float** inputs, float** outputs, int sampleframes);

typedef void (* setParameterFn)(AEffect* effect, int index, float parameter);

typedef float (* getParameterFn)(AEffect* effect, int index);

typedef AEffect*(* vstPluginMain)(audioMasterCallback audioMaster);

class wxDynamicLibrary;

#if defined(__WXMAC__)
struct __CFBundle;
typedef struct __CFBundle* CFBundleRef;
#if __LP64__
typedef int CFBundleRefNum;
#else
typedef signed short SInt16;
typedef SInt16 CFBundleRefNum;
#endif
#endif

class VST_API VSTLink /* not final */
{
public:
    virtual ~VSTLink() {}
    virtual intptr_t callDispatcher(int opcode, int index, intptr_t value, void* ptr, float opt) = 0;
};

struct VSTSettings
{
    // These are saved in the Config and checked against when loading a preset, to make sure
    // that we are loading a Config  which is compatible.
    //
    int32_t mUniqueID{};
    int32_t mVersion{};
    int32_t mNumParams{};

    // When loading a preset, the preferred way is to use the chunk; when not present in
    // the Config or failing to load, we fall back to loading single parameters (ID, value) pairs.
    //
    // It looks like a plugin might not support this (if their effFlagsProgramChunks bit is off)
    // If not, then hold an empty vector
    //
    std::vector<char> mChunk;

    // Fallback data used when the chunk is not available.
    std::unordered_map<wxString, std::optional<double> > mParamsMap;
};

struct VST_API VSTUIWrapper
{
    virtual void Idle();
    virtual void NeedIdle();
    virtual void SizeWindow(int w, int h);
    virtual void Automate(int index, float value);
    virtual void Flush();
};

struct VST_API VSTWrapper : public VSTLink, public XMLTagHandler, public VSTUIWrapper
{
    static inline VSTSettings& GetSettings(EffectSettings& settings)
    {
        auto pSettings = settings.cast<VSTSettings>();
        assert(pSettings);
        return *pSettings;
    }

    static inline const VSTSettings& GetSettings(const EffectSettings& settings)
    {
        auto pSettings = settings.cast<VSTSettings>();
        assert(pSettings);
        return *pSettings;
    }

    explicit VSTWrapper(const PluginPath& path)
        : mPath(path)
        , mMainThreadId{std::this_thread::get_id()}
    {}

    ~VSTWrapper();

    AEffect* mAEffect = nullptr;
    std::thread::id mMainThreadId;

    intptr_t callDispatcher(int opcode, int index, intptr_t value, void* ptr, float opt) override;

    intptr_t constCallDispatcher(int opcode, int index, intptr_t value, void* ptr, float opt) const;

    std::recursive_mutex mDispatcherLock;

    float callGetParameter(int index) const;

    void callSetChunk(bool isPgm, int len, void* buf);
    void callSetChunk(bool isPgm, int len, void* buf, VstPatchChunkInfo* info) const;

    int      GetString(wxString& outstr, int opcode, int index = 0) const;
    wxString GetString(int opcode, int index = 0) const;

    struct ParameterInfo
    {
        int mID;
        wxString mName;
    };

    //! @return true  continue visiting
    //! @return false stop     visiting
    using ParameterVisitor = std::function< bool (const ParameterInfo& pi) >;

    void ForEachParameter(ParameterVisitor visitor) const;

    bool FetchSettings(VSTSettings& vst3Settings, bool doFetch=true) const;

    bool StoreSettings(const VSTSettings& vst3settings) const;

    VstPatchChunkInfo GetChunkInfo() const;

    bool IsCompatible(const VstPatchChunkInfo&) const;

    // These are here because they are used by the import/export methods
    int mVstVersion;
    wxString mName;

    // XML load/save
    bool mInSet;
    bool mInChunk;
    wxString mChunk;
    long mXMLVersion;
    VstPatchChunkInfo mXMLInfo;

    bool LoadXML(const wxFileName& fn);
    bool HandleXMLTag(const std::string_view& tag, const AttributesList& attrs) override;
    void HandleXMLEndTag(const std::string_view& tag) override;
    void HandleXMLContent(const std::string_view& content) override;
    XMLTagHandler* HandleXMLChild(const std::string_view& tag) override;

    void SetString(int opcode, const wxString& str, int index = 0);

    ComponentInterfaceSymbol GetSymbol() const;

    void callSetParameter(int index, float value) const;

    void SaveXML(const wxFileName& fn) const;

    // Other formats for import/export
    bool LoadFXB(const wxFileName& fn);
    bool LoadFXP(const wxFileName& fn);
    bool LoadFXProgram(unsigned char** bptr, ssize_t& len, int index, bool dryrun);
    void callSetProgram(int index);

    void SaveFXB(const wxFileName& fn) const;
    void SaveFXP(const wxFileName& fn) const;
    void SaveFXProgram(wxMemoryBuffer& buf, int index) const;

    intptr_t mCurrentEffectID {};

    bool Load();
    PluginPath mPath;

    // Define a manager class for a handle to a module
#if defined(__WXMSW__)
    using ModuleHandle = std::unique_ptr<wxDynamicLibrary>;
#else
    struct ModuleDeleter {
        void operator()(void*) const;
    };
    using ModuleHandle = std::unique_ptr < char, ModuleDeleter >;
#endif
    ModuleHandle mModule{};

    wxString mVendor;
    wxString mDescription;
    int mVersion;
    bool mInteractive{ false };
    unsigned mAudioIns{ 0 };
    unsigned mAudioOuts{ 0 };
    int mMidiIns{ 0 };
    int mMidiOuts{ 0 };
    bool mAutomatable;

    void Unload();

    void ResetModuleAndHandle();

#if defined(__WXMAC__)
    // These members must be ordered after mModule

    using BundleHandle = CF_ptr<CFBundleRef>;

    BundleHandle mBundleRef;

    struct ResourceHandle {
        ResourceHandle(
            CFBundleRef pHandle = nullptr, CFBundleRefNum num = 0)
            : mpHandle{pHandle}, mNum{num}
        {}
        ResourceHandle& operator=(ResourceHandle&& other)
        {
            if (this != &other) {
                mpHandle = other.mpHandle;
                mNum = other.mNum;
                other.mpHandle = nullptr;
                other.mNum = 0;
            }
            return *this;
        }

        ~ResourceHandle() { reset(); }
        void reset();

        CFBundleRef mpHandle{};
        CFBundleRefNum mNum{};
    };
    ResourceHandle mResource;
#endif

    VstTimeInfo* GetTimeInfo();
    float        GetSampleRate();
    VstTimeInfo mTimeInfo;

    int mBufferDelay{ 0 };

    int GetProcessLevel();
    int mProcessLevel{ 1 }; // in GUI thread

    // The vst callback is currently called both for the effect and for instances.
    //
    static intptr_t AudioMaster(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt);

    // Some of the methods called by the callback make sense for the Effect:
    //
    // - All GUI-related stuff

    // Some other methods called by the callback make sense for Instances:
    virtual void SetBufferDelay(int samples);

    // Make message carrying all the information in settings, including chunks
    // This is called only on the main thread
    std::unique_ptr<EffectInstance::Message>
    MakeMessageFS(const VSTSettings& settings) const;

    // This is an immutable property determined once, when mAEffect is loaded
    // Whether the effect is capable of fancy native UI
    bool mGui{ false };
};

struct VSTMessage : EffectInstance::Message
{
    using ParamVector = std::vector<std::optional<double> >;

    // Make a message from a chunk and ID-value pairs
    explicit VSTMessage(std::vector<char> chunk, ParamVector params)
        : mChunk(std::move(chunk)),
        mParamsVec(std::move(params))
    {
    }

    // Make a message from a single parameter
    explicit VSTMessage(int id, double value, size_t numParams)
    {
        mParamsVec.resize(numParams, std::nullopt);
        if (id < numParams) {
            mParamsVec[id] = value;
        }
    }

    ~VSTMessage() override;

    std::unique_ptr<Message> Clone() const override;
    void Assign(Message&& src) override;
    void Merge(Message&& src) override;

    std::vector<char> mChunk;
    ParamVector mParamsVec;
};

#endif
